# -- encoding:utf-8 --
import traceback
import json
from datetime import datetime
from tornado import web
import hashlib
import numpy as np


class DatetimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S')
        if type(obj) == np.ndarray:
            return list(obj)
        if type(obj) in (np.bool, np.bool_):
            return bool(obj)
        if type(obj) in (np.int, np.int_):
            return int(obj)
        if type(obj) in (np.float, np.float_):
            return float(obj)
        if type(obj) == np.str:
            return str(obj)
        return json.JSONEncoder.default(self, obj)


def jsonable(func):
    def _call_func(*args, **kwargs):
        handler = args[0]
        is_web_handler = isinstance(handler, web.RequestHandler)
        res = {}
        try:
            res['result'] = func(*args, **kwargs)
            res['err_code'] = 0
            res['err_msg'] = None
        except Exception as e:
            s = str(traceback.format_exc())
            print(s)
            res['err_msg'] = e.err_msg if hasattr(e, 'err_msg') else (str(e) + ':\n' + s)
            res['err_code'] = e.err_code if hasattr(e, 'err_code') else 9999
        if len(args) == 0:
            return res

        if is_web_handler:
            _cors(handler)
            handler.set_header("Content-type", "application/json")
            handler.write(json.dumps(res, cls=DatetimeEncoder))
        else:
            return res

    return _call_func


def _cors(handler: web.RequestHandler):
    if 'Origin' in handler.request.headers:
        origin = handler.request.headers['Origin']
        handler.set_header("Access-Control-Allow-Origin", origin)
        handler.set_header("Access-Control-Allow-Credentials", "true")
        # handler.set_header("Access-Control-Allow-Methods", "POST, GET")
        # handler.set_header("Access-Control-Allow-Headers", "x-requested-with")


def binary_handler(func):
    """
    The decoration is used to decorate the get() or the post() function of a tornado.web.RequestHandler.
    The function is expected to return a tuple of (Data, Content-Type-String), where the data is
    responded to the client side, the Content-Type-String is the content type of the data.
    :param func: the function which is decorated by this decoration
    """

    def _call_func(*args, **kwargs):
        try:
            result = func(*args, **kwargs)
        except Exception as e:
            if len(args) == 0:
                raise e
            s = str(traceback.format_exc())
            print(s)
            # args[0].send_error(500, reason=s.replace('\n', '\\n'))
            res = {'err_msg': e.err_msg if hasattr(e, 'err_msg') else s,
                   'err_code': e.err_code if hasattr(e, 'err_code') else 9999}
            handler = args[0]
            if isinstance(handler, web.RequestHandler):
                _cors(handler)
                handler.set_header("Content-type", "application/json")
                handler.write(json.dumps(res, cls=DatetimeEncoder))
                return
            else:
                return res

        if len(args) == 0:
            return result

        handler = args[0]
        if isinstance(handler, web.RequestHandler):
            _cors(handler)
            handler.set_header("Content-type", result[1])
            handler.write(result[0])
        else:
            return result

    return _call_func


if __name__ == '__main__':
    s = []
    for _ in range(5):
        m = hashlib.md5(str(datetime.now().timestamp()).encode())
        s.append(m)
    for m in s:
        print(m.hexdigest())


    class _My:
        def __init__(self):
            print('in _My.__init__()', flush=True)

        @jsonable
        def _test(self):
            print('in _test')


    my = _My()
    print(my._test)
    print(my._test())
